refactor(alerts): unify on the v2 alert system - #293
Conversation
Ingest Rust Test + Benchmark ResultsCommit: Load Benchmark —
|
| Metric | main (median) | PR (median) | Delta |
|---|---|---|---|
| Requests/sec | 11086.22 | 10449.08 | -5.7% worse |
| Rows/sec | 110862.19 | 104490.84 | -5.7% worse |
| p50 latency | 5.49 ms | 5.86 ms | +6.7% worse |
| p95 latency | 10.81 ms | 11.69 ms | +8.1% worse |
| p99 latency | 12.98 ms | 14.04 ms | +8.1% worse |
| Export catch-up | 0.027 s | 0.027 s | -0.2% better |
| Max RSS | 15.59 MiB | 16.45 MiB | +5.6% worse |
| Failures | 0 | 0 | same |
Same code path on both sides (same LOAD_TEST_INGEST_MODE), so the delta column is meaningful. Numbers come from ubuntu-latest, which is noisy — treat single-digit-percent deltas as noise.
PR load benchmark JSON (per-iteration)
[
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 11,
"duration_seconds": 0.191404342,
"export_catchup_seconds": 0.025095261,
"request_rps": 10449.083751715518,
"row_rps": 104490.83751715517,
"p50_ms": 5.861,
"p95_ms": 10.876,
"p99_ms": 12.875,
"max_rss_mb": 18.12109375,
"max_cpu_percent": 20.0,
"avg_cpu_percent": 20.0
},
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 11,
"duration_seconds": 0.185584241,
"export_catchup_seconds": 0.026502793,
"request_rps": 10776.77710792265,
"row_rps": 107767.77107922648,
"p50_ms": 5.833,
"p95_ms": 11.694,
"p99_ms": 14.08,
"max_rss_mb": 15.64453125,
"max_cpu_percent": 0.0,
"avg_cpu_percent": 0.0
},
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 12,
"duration_seconds": 0.20961849,
"export_catchup_seconds": 0.02662882,
"request_rps": 9541.14305469904,
"row_rps": 95411.43054699039,
"p50_ms": 6.442,
"p95_ms": 12.525,
"p99_ms": 14.037,
"max_rss_mb": 16.453125,
"max_cpu_percent": 25.0,
"avg_cpu_percent": 25.0
}
]main load benchmark JSON (per-iteration)
[
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 11,
"duration_seconds": 0.180404154,
"export_catchup_seconds": 0.026648533,
"request_rps": 11086.219223089507,
"row_rps": 110862.19223089507,
"p50_ms": 5.57,
"p95_ms": 10.815,
"p99_ms": 12.984,
"max_rss_mb": 15.953125,
"max_cpu_percent": 0.0,
"avg_cpu_percent": 0.0
},
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 10,
"duration_seconds": 0.165657708,
"export_catchup_seconds": 0.026567801,
"request_rps": 12073.087477462866,
"row_rps": 120730.87477462867,
"p50_ms": 4.978,
"p95_ms": 9.875,
"p99_ms": 11.769,
"max_rss_mb": 15.5859375,
"max_cpu_percent": 0.0,
"avg_cpu_percent": 0.0
},
{
"ingest_mode": "tinybird",
"requests": 2000,
"successes": 2000,
"failures": 0,
"rows_sent": 20000,
"rows_exported": 20000,
"imports": 11,
"duration_seconds": 0.18089209,
"export_catchup_seconds": 0.026307864,
"request_rps": 11056.315397760067,
"row_rps": 110563.15397760067,
"p50_ms": 5.491,
"p95_ms": 11.345,
"p99_ms": 15.98,
"max_rss_mb": 15.34765625,
"max_cpu_percent": 0.0,
"avg_cpu_percent": 0.0
}
]WAL-acked microbench (cargo bench --bench ingest_bench)
Compiling maple-ingest v0.1.0 (/home/runner/_work/maple/maple/apps/ingest)
Finished `bench` profile [optimized] target(s) in 17.71s
Running benches/ingest_bench.rs (target/release/deps/ingest_bench-f6ec3881b8bd23cb)
Gnuplot not found, using plotters backend
test ingest_accept/logs_10_rows_wal_ack ... bench: 136504 ns/iter (+/- 18339)
test ingest_accept/traces_10_spans_wal_ack ... bench: 168879 ns/iter (+/- 30132)
cargo test
test result: ok. 66 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.73s
Running unittests src/bin/load_test.rs (target/debug/deps/load_test-3a24fd5dee8f57f0)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/maple_ingest-b89fa52019d5d48f)
running 60 tests
test autumn::tests::flat_hardcap_depleted_blocks ... ok
test autumn::tests::allowed_only_no_balance_field ... ok
test autumn::tests::flat_hardcap_with_remaining_allows ... ok
test autumn::tests::flat_overage_allows ... ok
test autumn::tests::flat_sub_one_gb_remaining_still_allows ... ok
test autumn::tests::nested_balance_object_depleted_blocks ... ok
test autumn::tests::flat_unlimited_allows ... ok
test autumn::tests::nested_balance_object_with_remaining_allows ... ok
test autumn::tests::nested_overage_allows ... ok
test autumn::tests::unrecognized_shape_returns_none ... ok
test autumn::tests::null_balance_no_subscription_blocks ... ok
test tests::a_browser_sessions_cap_blocks_replay_without_touching_other_signals ... ok
test tests::api_error_from_pipeline_maps_variants_to_status ... ok
test tests::api_error_kind_maps_status_to_stable_label ... ok
test tests::caller_fault_rejections_do_not_mark_the_span_error ... ok
test tests::clickhouse_destination_is_terminal_in_dual_mode ... ok
test tests::clickhouse_destination_uses_native_pipeline_even_in_forward_mode ... ok
test tests::clickhouse_target_resolver_decrypts_current_schema_password ... ok
test tests::clickhouse_target_resolver_rejects_password_over_http ... ok
test tests::clickhouse_target_resolver_requires_current_schema ... ok
test tests::cloudflare_ndjson_payload_parses_multiple_records ... ok
test tests::cloudflare_log_record_maps_body_severity_and_attributes ... ok
test tests::cloudflare_timestamps_support_rfc3339_unix_and_unix_nano ... ok
test tests::cloudflare_validation_payload_is_detected ... ok
test tests::data_loss_labels_match_the_kinds_the_gateway_emits ... ok
test tests::data_loss_rejections_mark_the_span_error_even_at_4xx ... ok
test tests::decrypt_aes256_gcm_matches_node_crypto_fixture ... ok
test tests::empty_export_request_is_accepted ... ok
test tests::enrichment_applies_to_a_payload_with_no_resource ... ok
test tests::enrichment_overwrites_tenant_fields ... ok
test tests::extract_ingest_key_returns_sentinel_literal_unchanged ... ok
test tests::hash_is_deterministic ... ok
test tests::lenient_log_payload_is_accepted ... ok
test tests::malformed_payload_is_rejected_with_the_parser_reason ... ok
test autumn::tests::fails_open_on_transport_error ... ok
test autumn::tests::fail_open_decisions_are_not_cached ... ok
test tests::numeric_nanos_no_longer_silently_empty_a_metric ... ok
test tests::pipeline_error_fault_split_matches_http_status ... ok
test tests::rejection_reason_is_bounded ... ok
test tests::replay_budget_disabled_when_limit_is_zero ... ok
test tests::replay_budget_scopes_totals_per_session_and_org ... ok
test tests::replay_budget_truncates_session_at_its_ceiling ... ok
test tests::resolve_connector_refreshes_routing_before_auth_cache_expires ... ok
test tests::resolve_ingest_key_carries_spend_pause_state_from_the_key_lookup ... ok
test tests::resolve_ingest_key_keeps_stale_schema_on_managed_native_path ... ok
test tests::native_request_emits_a_span_per_pipeline_stage ... ok
test tests::resolve_ingest_key_returns_none_when_hash_missing ... ok
test tests::resolve_ingest_key_returns_self_managed_false_when_no_settings_row ... ok
test tests::resolve_ingest_key_returns_self_managed_true_when_active_settings_row ... ok
test tests::replay_metadata_is_rejected_and_unmetered_when_sessions_are_capped ... ok
test tests::sentinel_token_matches_only_exact_literal ... ok
test tests::server_faults_stay_error ... ok
test tests::spec_compliant_payloads_still_decode ... ok
test tests::spend_blocks_signal_pauses_the_whole_org_or_just_the_capped_signal ... ok
test tests::tinybird_destination_keeps_forward_mode_on_forward_path ... ok
test tests::resolve_ingest_key_refreshes_routing_before_auth_cache_expires ... ok
test tests::resolve_ingest_key_serves_last_known_routing_when_refresh_fails ... ok
test tests::forward_mode_switches_ready_org_to_clickhouse_without_forwarding_again ... ok
test autumn::tests::usage_after_a_successful_flush_gets_a_fresh_key ... ok
test autumn::tests::a_failed_flush_retries_under_the_same_idempotency_key ... ok
test result: ok. 60 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.03s
Doc-tests maple_ingest
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
|
Your Pullfrog Router balance is exhausted. You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer. Top up balance → · Enable auto-reload →
|
|
Your Pullfrog Router balance is exhausted. You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer. Top up balance → · Enable auto-reload →
|
|
Your Pullfrog Router balance is exhausted. You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer. Top up balance → · Enable auto-reload →
|
Migrations created tables with owner-only privileges, so every new or rebuilt table had to be healed by a post-migrate grant pass keyed to MAPLE_PG_RUNTIME_ROLE. Forgetting that env var on main is what caused the 2026-07-29 ingest outage, and the hard failure guarding it made the prod migration unrunnable without hunting down a role name by hand. Pin the migration session to `postgres` (ALTER ROLE ... SET role, so it applies to the connections drizzle-kit opens itself) and install default privileges granting PUBLIC before migrating. New tables are then born with correct grants and the sweep is only a backfill for pre-existing objects. PUBLIC rather than a named role is what actually matters: three of prod's four pscale_api_* login roles are members of `postgres` with rolinherit and read a postgres-owned table through inheritance, but the ingest gateway connects via PSBouncer as pscale_api_rg068pnctlxw, which is not a member and reads only through PUBLIC. That asymmetry is why the outage broke the gateway alone while the API and alerting workers kept serving. - replace grant-runtime-role.ts with ensure-privileges.ts (db:ensure-privileges) - drop the MAPLE_PG_RUNTIME_ROLE guard and env var entirely - stg/pr-preview workflows run the step BEFORE migrate; ordering is load-bearing since default privileges only affect objects created after Verified on pr-152: a table created after the change goes from relacl=null to PUBLIC arwd with no grant pass and no configuration.
|
Your Pullfrog Router balance is exhausted. You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer. Top up balance → · Enable auto-reload →
|

Summary
0026_windy_bromleyto convert metric rules and delete retired destination dataWhy
Alerts had parallel HTTP, evaluation, storage, provider, and UI paths. This hard cutover leaves one Postgres-backed alert service and one shared query-engine evaluation path.
User and operator impact
builder_querydrafts.slackandhazeldestinations, plus their delivery history, are deleted.slack-botorhazel-oauth./api/alertsis removed; clients must use/v2/alerts/*.Deployment warning
This is a destructive, forward-only hard cutover. Production rollout should:
0026_windy_bromleythrough the direct PlanetScale connection with runtime grants/v2/alerts/{rules,destinations,incidents,deliveries}and the schedulerReverting after the migration requires a database restore or a forward fix.
Validation
bun typecheck— 33 packages passedgit diff --check— cleanNeed help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.